package tv.dyndns.kishibe.server.handwriting;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.StackObjectPool;

import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.sun.jna.Pointer;

public class RecognizerZinnia implements Recognizable {
	private static final int SIZE = 300;
	private volatile Set<String> availableCharacters;
	// recognizerのインスタンスはObjectPoolに保持させる
	private final ZinniaLibrary zinnia;
	private final ObjectPool<Pointer> pool;

	@Inject
	public RecognizerZinnia(ZinniaLibrary zinnia, ZinniaObjectFactory zinniaObjectFactory) {
		this.zinnia = zinnia;
		this.pool = new StackObjectPool<Pointer>(zinniaObjectFactory);
	}

	private void initializeIfNotInitialized() {
		if (availableCharacters == null) {
			synchronized (this) {
				if (availableCharacters == null) {
					availableCharacters = Sets.newHashSet();

					Pointer recognizer = null;
					try {
						recognizer = (Pointer) pool.borrowObject();
						int sizeOfRecognizer = (int) zinnia.zinnia_recognizer_size(recognizer);
						for (int i = 0; i < sizeOfRecognizer; ++i) {
							availableCharacters.add(zinnia.zinnia_recognizer_value(recognizer, i));
						}
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						if (recognizer != null) {
							try {
								pool.returnObject(recognizer);
							} catch (Exception e) {
								e.printStackTrace();
							}
						}
					}

				}
			}
		}
	}

	@Override
	public String[] recognize(double[][][] inputStrokes) {
		initializeIfNotInitialized();

		Pointer character = zinnia.zinnia_character_new();
		zinnia.zinnia_character_clear(character);
		zinnia.zinnia_character_set_width(character, SIZE);
		zinnia.zinnia_character_set_height(character, SIZE);
		int strokeIndex = 0;
		for (double[][] stroke : inputStrokes) {
			for (double[] p : stroke) {
				int x = (int) (p[0] * SIZE + 0.5);
				int y = (int) (p[1] * SIZE + 0.5);
				zinnia.zinnia_character_add(character, strokeIndex, x, y);
			}
			++strokeIndex;
		}

		Pointer recognizer = null;
		Pointer result = null;
		try {
			recognizer = (Pointer) pool.borrowObject();

			result = zinnia.zinnia_recognizer_classify(recognizer, character, 12);

			if (result == null) {
				System.err.printf("%s\n", zinnia.zinnia_recognizer_strerror(recognizer));
				return null;
			}

			List<String> values = new ArrayList<String>();
			for (int i = 0; i < zinnia.zinnia_result_size(result); ++i) {
				String value = zinnia.zinnia_result_value(result, i);
				values.add(value);
			}

			return values.toArray(new String[0]);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (recognizer != null) {
					pool.returnObject(recognizer);
					recognizer = null;
				}
			} catch (Exception e) {
				e.printStackTrace();
			}

			if (result != null) {
				zinnia.zinnia_result_destroy(result);
			}

			if (character != null) {
				zinnia.zinnia_character_destroy(character);
			}
		}

		return null;
	}

	@Override
	public Set<String> getAvailableCharacters() {
		initializeIfNotInitialized();

		return availableCharacters;
	}
}
